/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.service.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.codec.digest.DigestUtils;

import sh.grapendaal.tsuushin.core.IrcBot;
import sh.grapendaal.tsuushin.message.IncomingMessage;
import sh.grapendaal.tsuushin.message.MessageType;
import sh.grapendaal.tsuushin.message.PrivateMessage;
import sh.grapendaal.tsuushin.message.User;
import sh.grapendaal.tsuushin.permission.PermissionData;
import sh.grapendaal.tsuushin.permission.PermissionString;
import sh.grapendaal.tsuushin.service.PermissionService;

import com.google.inject.Singleton;

@Singleton
public class PermissionServiceImpl implements PermissionService, IrcBot.ReceiveListener {
	private static final String PREFIX_LOGIN_COMMAND = "login ";

	private Set<PermissionString> permissions = new HashSet<PermissionString>();
	private Map<User, PermissionData> users = new Hashtable<User, PermissionData>();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean registerPermission(String permission) {
		return permissions.add(new PermissionString(permission));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean unregisterPermission(String permission) {
		return permissions.remove(new PermissionString(permission));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean permissionExists(String permission) {
		return permissions.contains(new PermissionString(permission));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<String> getUsablePermissions() {
		List<String> list = new ArrayList<String>();
		for (PermissionString permission : permissions) {
			list.add(permission.toString());
		}
		return list;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addUser(User user) {
		this.addUser(user, null);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addUser(User user, String hashedPassword) {
		users.put(user, new PermissionData(user, hashedPassword));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeUser(User user) {
		users.remove(user);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isUserKnown(User user) {
		return users.containsKey(user);
	}

	/**
	 * Attempts to identify the given user with the given password.
	 * 
	 * @param user
	 *            The user who tried to identify.
	 * @param password
	 *            The password to identify with.
	 * @return Boolean indicating whether identification was successful.
	 */
	boolean identifyUser(User user, String password) {
		if (isUserKnown(user)) {
			String input = DigestUtils.shaHex(password);
			PermissionData data = users.get(user);
			if (data.getHashedPassword().equals(input)) {
				data.setIdentified(true);
				return true;
			}
		}

		return false;
	}

	/**
	 * Logs out the given user if he was identified.
	 * 
	 * @param user
	 *            The user to log out.
	 * @return Boolean indicating whether the user was successfully logged out.
	 */
	boolean logoutUser(User user) {
		if (isUserKnown(user) && users.get(user).isIdentified()) {
			users.get(user).setIdentified(false);
			return true;
		}

		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean grantPermission(User user, String permission) {
		checkPermissionAndUser(user, permission);

		return users.get(user).grantPermission(new PermissionString(permission));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean revokePermission(User user, String permission) {
		checkPermissionAndUser(user, permission);

		return users.get(user).revokePermission(new PermissionString(permission));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasPermission(User user, String permission) {
		checkPermissionAndUser(user, permission);

		return users.get(user).hasPermission(new PermissionString(permission));
	}

	/**
	 * Checks whether we know the given permission and user and throws an
	 * IllegalArgumentException if not.
	 * 
	 * @param user
	 *            The user to check.
	 * @param permission
	 *            The permission to check.
	 */
	private void checkPermissionAndUser(User user, String permission) {
		if (!this.isUserKnown(user)) {
			throw new IllegalArgumentException("User " + user + " not known in Permission Service.");
		}
		if (!this.permissionExists(permission)) {
			throw new IllegalArgumentException("Permission \"" + permission
					+ "\" not registered in Permission Service.");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isBotOwner(User user) {
		return this.hasPermission(user, PermissionService.PERMISSION_BOT_OWNER);
	}

	@Override
	public boolean onReceive(IrcBot bot, IncomingMessage message) {
		if (message.getMessageType() != MessageType.PRIVATE_MESSAGE) {
			return true;
		}

		if (message.getContent().startsWith(PREFIX_LOGIN_COMMAND)) {
			if (identifyUser(message.getUser(), message.getContent().substring(PREFIX_LOGIN_COMMAND.length()))) {
				PrivateMessage confirmation = new PrivateMessage();
				confirmation.setDestination(message.getUser().getNickname());
				confirmation.setContent("You have successfully identified yourself.");
				bot.send(confirmation);
			}

			// Discard any potential login command.
			return false;
		}

		return true;
	}
}
